//@author A0083782W
#include "EventTime.h"


bool Time::isCompletedDate () const {
	for (pos_int i = (pos_int)TimeType::YEAR; 
		i <= (pos_int)TimeType::DAY; i++) {
		if (timeData[i] == 0) {
			return false;
		}
	}
	return true;
}

bool Time::canBeSet (TimeType requiredType, ByteInt newValue) {
	switch (requiredType) {
	case TimeType::SECOND:
	case TimeType::MINUTE:
		if (newValue > MAX_MINUTE) {
			return false;
		}
		else {
			return true;
		}
		break;
	case TimeType::HOUR:
		if (newValue > MAX_HOUR) {
			return false;
		}
		else {
			return true;
		}
		break;
	case TimeType::DAY:
		if (newValue > MAX_DAY_PLUS) {
			return false;
		}
		else {
			return true;
		}
		break;
	case TimeType::MONTH:
		if (newValue > MAX_MONTH) {
			return false;
		}
		else {
			return true;
		}
		break;
	case TimeType::YEAR:
		if (newValue > MAX_YEAR) {
			return false;
		}
		else {
			return true;
		}
		break;
	case TimeType::DAYOFWEEK:
		if (newValue > MAX_WEEK) {
			return false;
		}
		else {
			return true;
		}
	default:
		return false;
		break;
	}
}

Time::Time() {
	reset();
}

bool Time::isSecondDisplayable() const {
	return _secondDisplayable;
}

void Time::setSecondDisplay(bool newBool) {
	_secondDisplayable = newBool;
}

void Time::reset() {
	for (ByteInt i = 0; i < TIME_MAX; i++) {
		timeData[i] = 0;
	}
	timeData[(int)TimeType::DAYOFWEEK] = (ByteInt)WeekDay::UNCLEAR;
	_lastSet = false;
	_secondDisplayable = false;
}

Time::Time(const Time& anotherTime) {
	for (ByteInt i = 0; i < TIME_MAX; i++) {
		timeData[i] = anotherTime.timeData[i];
	}
	_lastSet = false;
	_secondDisplayable = anotherTime._secondDisplayable;
}

bool Time::setTime (TimeType requiredType, ByteInt newValue) {
	if (Time::canBeSet (requiredType, newValue) == false) {
		return false;
	}
	if (requiredType == TimeType::DAYOFWEEK) {
		return false;
	}
	
	timeData[(int)(requiredType)] = newValue;
	if (!(checkAndFixDate())) {
		_lastSet = false;
	}
	else {
		_lastSet = true;
	}

	if (isCompletedDate()) {
		generateDayOfWeek();
	}

	return true;
}

ByteInt Time::getTime (TimeType requiredType) const {
	return timeData[(int)requiredType];
}

bool Time::isLastInputLegal () const {
	return _lastSet;
}

bool Time::isEmpty() const {
	if (isCompletedDate() == false &&
		timeData[(pos_int)TimeType::HOUR] == 0 &&
		timeData[(pos_int)TimeType::MINUTE] == 0 &&
		timeData[(pos_int)TimeType::SECOND] == 0 &&
		timeData[(pos_int)TimeType::DAYOFWEEK] == (pos_int)(WeekDay::UNCLEAR)) {
			return true;
	}
	else {
		return false;
	}
}

bool Time::operator== (Time const & rightOperand) const {
	for (ByteInt i = FIRST_MEMBER; i < TIME_MAX; i++) {
		if ( rightOperand.getTime ((TimeType)(i)) != 
			this->getTime ((TimeType)(i))) {
				return false;
		}
	}
	return true;
}

bool Time::operator< (const Time& rightOperand) const {
	ByteInt rightTime = 0;
	ByteInt myTime = 0;
	for (ByteInt i = FIRST_MEMBER; i < TIME_MAX; i++) {
		rightTime = rightOperand.getTime ((TimeType)(i));
		myTime = this->getTime ((TimeType)(i));
		if (myTime < rightTime) {
			return true;
		}
		else if ( myTime > rightTime ) {
			return false;
		}
	}
	return false;
}

bool Time::operator> (const Time& rightOperand) const {
	ByteInt rightTime = 0;
	ByteInt myTime = 0;
	for (ByteInt i = FIRST_MEMBER; i < TIME_MAX; i++) {
		rightTime = rightOperand.getTime ((TimeType)(i));
		myTime = this->getTime ((TimeType)(i));
		if (myTime > rightTime) {
			return true;
		}
		else if (myTime < rightTime) {
			return false;
		}
	}
	return false;
}

bool Time::operator>= (const Time& rightOperand) const {
	ByteInt rightTime = 0;
	ByteInt myTime = 0;
	for (ByteInt i = FIRST_MEMBER; i < TIME_MAX; i++) {
		rightTime = rightOperand.getTime ((TimeType)(i));
		myTime = this->getTime ((TimeType)(i));
		if (myTime > rightTime) {
			return true;
		}
		else if (myTime < rightTime) {
			return false;
		}
	}
	return true;
}

bool Time::operator<= (const Time& rightOperand) const {
	ByteInt rightTime = 0;
	ByteInt myTime = 0;
	for (ByteInt i = FIRST_MEMBER; i < TIME_MAX; i++) {
		rightTime = rightOperand.getTime ((TimeType)(i));
		myTime = this->getTime ((TimeType)(i));
		if (myTime < rightTime) {
			return true;
		}
		else if ( myTime > rightTime ) {
			return false;
		}
	}
	return true;
}

bool Time::operator!= (const Time& rightOperand) const {
	for (ByteInt i = FIRST_MEMBER; i < TIME_MAX; i++) {
		if ( rightOperand.getTime ((TimeType)(i)) != 
			this->getTime ((TimeType)(i))) {
				return true;
		}
	}
	return false;
}

bool Time::operator |= (const Time& rightOperand) const {
	if (getTime(TimeType::HOUR) == rightOperand.getTime(TimeType::HOUR) &&
		getTime(TimeType::MINUTE) == rightOperand.getTime(TimeType::MINUTE) &&
		getTime(TimeType::SECOND) == rightOperand.getTime(TimeType::SECOND)) {
			return true;
	}
	else {
		return false;
	}
}

bool Time::operator &= (const Time& rightOperand) const {
	if (getTime (TimeType::YEAR) == rightOperand.getTime (TimeType::YEAR) &&
		getTime (TimeType::MONTH) == rightOperand.getTime (TimeType::MONTH) &&
		getTime (TimeType::DAY) == rightOperand.getTime (TimeType::DAY)) {
			return true;
	}
	else {
		return false;
	}
}

void Time::operator++ (int) {
	if (isCompletedDate()) {
		const pos_int LIMIT = getMaxDay (getTime (TimeType::YEAR),
			(Month)getTime (TimeType::MONTH));
		if (getTime (TimeType::DAY) == LIMIT) {
			if (getTime(TimeType::MONTH) == (int)(Month::DECEMBER)) {
				timeData[(int)(TimeType::YEAR)]++;
				timeData[(int)(TimeType::MONTH)] = (ByteInt)(Month::JANUARY);
			}
			else {
				timeData[(int)(TimeType::MONTH)]++;
			}
			timeData[(int)(TimeType::DAY)] = 1;
		}
		else {
			timeData[(int)(TimeType::DAY)]++;
		}
		if (timeData[(int)(TimeType::DAYOFWEEK)] != (int) WeekDay::UNCLEAR) {
			timeData[(int)(TimeType::DAYOFWEEK)]++ ;
			timeData[(int)(TimeType::DAYOFWEEK)] %= MAX_WEEK;
		}
	} 
}

void Time::operator-- (int) {
	if (isCompletedDate()) {
		if (getTime (TimeType::DAY) == 1) {
			if (getTime(TimeType::MONTH) == (int)(Month::JANUARY)) {
				timeData[(int)(TimeType::YEAR)]--;
				timeData[(int)(TimeType::MONTH)] = (ByteInt)(Month::DECEMBER);
			}
			else {
				timeData[(int)(TimeType::MONTH)]--;
			}
			const pos_int LIMIT = getMaxDay (timeData[(int)(TimeType::YEAR)],
				(Month)timeData[(int)(TimeType::MONTH)]);
			timeData[(int)(TimeType::DAY)] = LIMIT;
		}
		else {
			timeData[(int)(TimeType::DAY)]--;
		}
		if (timeData[(int)(TimeType::DAYOFWEEK)] != (int) WeekDay::UNCLEAR) {
			if(timeData[(int)(TimeType::DAYOFWEEK)] ==0 ) {
				timeData[(int)(TimeType::DAYOFWEEK)] = 6;
			}
			else {
				timeData[(int)(TimeType::DAYOFWEEK)]--;
			}
		}
	} 
}

void Time::operator= (const CurrentTime& rightOperand) {
	for (pos_int i = FIRST_MEMBER; i < TIME_MAX; i++) {
		timeData[i] = rightOperand.getCurrentTime ((TimeType)(i));
	}
}

/*string Time::toString () const{
	string result = "";

	if (timeData[(int)TimeType::HOUR] < 10) { 
		result += "0"; 
	}
	result += to_string(timeData[(int)TimeType::HOUR]);
	result += ":";
	if (timeData[(int)TimeType::MINUTE] < 10) { 
		result += "0"; 
	}
	result += to_string(timeData[(int)TimeType::MINUTE]);
	result += " ";
	result += to_string(timeData[(int)TimeType::DAY]) + "/" + to_string(timeData[(int)TimeType::MONTH]) + 
		"/20" + to_string (timeData[(int)(TimeType::YEAR)]);
	result += " ";
	result += WEEKDAY_LIST[timeData[(int)TimeType::DAYOFWEEK]][0];
	return result;
}*/

string Time::toDayOfWeekString() const {
	if (timeData[(int)TimeType::DAYOFWEEK] != (int)WeekDay::UNCLEAR) {
		string result;
		result = WEEKDAY_LIST[timeData[(int)TimeType::DAYOFWEEK]][0];
		capitaliseFirst (result);
		return result;
	}
	else {
		return "";
	}
}

string Time::toTimeString() const {
	string result = "";
	ReadSettingPtr config = &Settings::get();
	switch (config->getTimeDisplayFormat()) {
	case TimeDisplayFormat::AM_PM:
		if (timeData [(pos_int) TimeType::HOUR] > MAX_HALF_HOUR) {
			result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::HOUR] - MAX_HALF_HOUR));
			result.push_back (TIME_SEP_CHAR[0]);
			result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::MINUTE]));
			if (_secondDisplayable) {
				result.push_back(TIME_SEP_CHAR[0]);
				result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::SECOND]));
			}
			result += AM_PM[2][0];
		}
		else if (timeData [(pos_int) TimeType::HOUR] == MAX_HALF_HOUR) {
			result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::HOUR]));
			result.push_back (TIME_SEP_CHAR[0]);
			result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::MINUTE]));
			if (_secondDisplayable) {
				result.push_back(TIME_SEP_CHAR[0]);
				result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::SECOND]));
			}
			result += AM_PM[2][0];
		}
		else {
			result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::HOUR]));
			result.push_back(TIME_SEP_CHAR[0]);
			result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::MINUTE]));
			if (_secondDisplayable) {
				result.push_back(TIME_SEP_CHAR[0]);
				result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::SECOND]));
			}
			result += AM_PM[1][0];
		}
		break;
	case TimeDisplayFormat::TWENTY_FOUR:
		result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::HOUR]));
		result.push_back(TIME_SEP_CHAR[0]);
		result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::MINUTE]));
		if (_secondDisplayable) {
				result.push_back(TIME_SEP_CHAR[0]);
				result += standardisedString (to_string ((pos_int)timeData [(pos_int) TimeType::SECOND]));
		}
		break;
	default:
		break;
	}
	return result;
}

string Time::toDateString() const {
	string result = "";
	ReadSettingPtr config = &Settings::get();

	switch (config->getDateDiplayFormat())
	{
	case DateDisplayFormat::DDMMYY:
		result += to_string((int) timeData [(int)TimeType::DAY]);
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) timeData [(int)TimeType::MONTH]);
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) timeData [(int)TimeType::YEAR]);
		break;
	case DateDisplayFormat::DDMMYYYY:
		result += to_string((int) timeData [(int)TimeType::DAY]);
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) timeData [(int)TimeType::MONTH]);
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) (timeData [(int)TimeType::YEAR] + FOUR_DIGIT_YEAR));
		break;
	case DateDisplayFormat::MMDDYY:
		result += to_string((int) timeData [(int)TimeType::MONTH]);
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) timeData [(int)TimeType::DAY]);
		result.push_back (DATE_SEP_CHAR[0]); 
		result += to_string((int) timeData [(int)TimeType::YEAR]);
		break;
	case DateDisplayFormat::MMDDYYYY:
		result += to_string((int) timeData [(int)TimeType::MONTH]);
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) timeData [(int)TimeType::DAY]);
		result.push_back (DATE_SEP_CHAR[0]); 
		result += to_string((int) (timeData [(int)TimeType::YEAR] + FOUR_DIGIT_YEAR));
		break;
	case DateDisplayFormat::YYMMDD:
		result += to_string((int) timeData [(int)TimeType::YEAR]);
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) timeData [(int)TimeType::MONTH]);
		result.push_back (DATE_SEP_CHAR[0]); 
		result += to_string((int) timeData [(int)TimeType::DAY]);
		break;
	case DateDisplayFormat::YYYYMMDD:
		result += to_string((int) (timeData [(int)TimeType::YEAR] + 2000));
		result.push_back (DATE_SEP_CHAR[0]);
		result += to_string((int) timeData [(int)TimeType::MONTH]);
		result.push_back (DATE_SEP_CHAR[0]); 
		result += to_string((int) timeData [(int)TimeType::DAY]);
		break;
	default:
		break;
	}
	return result;
}

void Time::generateDayOfWeek () {
	CurrentTime myClock;
	Time tempTime = *this;
	timeData[(int)TimeType::DAY] = myClock.getCurrentTime(TimeType::DAY);
	timeData[(int)TimeType::MONTH] = myClock.getCurrentTime(TimeType::MONTH);
	timeData[(int)TimeType::YEAR] = myClock.getCurrentTime(TimeType::YEAR);
	timeData[(int)TimeType::DAYOFWEEK] = myClock.getCurrentTime(TimeType::DAYOFWEEK);
	if (tempTime >= *this) {
		while (!(*this &= tempTime)) {
			(*this)++;
		}
	}
	else {
		while (!(*this &= tempTime)) {
			(*this)--;
		}
	}
}

bool Time::checkAndFixDate() {
	pos_int limit;

	limit = getMaxDay (timeData[(int)TimeType::YEAR],
			(Month)timeData[(int)TimeType::MONTH]);

	if ( timeData[(int)TimeType::DAY] > limit) {
		timeData[(int)TimeType::DAY] = limit;
		return false;
	}
	return true;
}

static string standardisedString(con_str& mainString) {
	if (mainString.size() == 1) {
		return "0" + mainString;
	}
	else {
		return mainString;
	}
}

